/*******************************************************************************
*  file    : archivedb.cpp
*  created : 19.08.2013
*  author  :
*******************************************************************************/

#include "archivedb.hpp"
#include "application.hpp"
#include <QSqlDatabase>
#include <QScopedPointer>
#include <QStringList>
#include <QSqlQuery>
#include <QSqlError>
#include <QFile>
#include <qlogger.hpp>
#include <memory>

namespace{
QLogger* l = QLoggerEngine::getLogger("core");

QScopedPointer<ArchiveDB> archive_db;
}

extern char tbl_devices  [];
extern char tbl_incoming [];
extern char tbl_outgoing [];
extern char tbl_tasks    [];
extern char tbl_version  [] ;
extern char set_scheme_version [];

extern char add_sleeper_1 [];
extern char add_sleeper_2 [];

ArchiveDB*ArchiveDB::instance()
{
    if( !archive_db.data() )
        archive_db.reset( new ArchiveDB );

    return archive_db.data();
}

bool ArchiveDB::connected() const
{
    return QSqlDatabase::database().isOpen();
}

const QString&ArchiveDB::databaseName() const
{
    return data_name_;
}

void ArchiveDB::setDatabaseName(const QString& name)
{
    if(data_name_ != name)
        {
            data_name_ = name;
        }
}

bool ArchiveDB::addMole(const QString &phone, const QString &name, const QString &passwd,
                        const QString &memo, const QString &bat_ch)
{
    QString add = QString("INSERT INTO devices "
                          "(phone_num, name,passwd, memo,bat_change, hidden)"
                          " VALUES ('%1', '%2', '%3', '%4','%5', 0);").arg(phone).arg(name).arg(passwd).arg(memo).arg(bat_ch);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(add) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    is_mole_cached_ = false;
    return true;
}

bool ArchiveDB::updateMole(const QString &phone, const QString &name, const QString &passwd,
                           const QString &memo, const QString &bat_ch, int id)
{
    QString upd = QString("UPDATE devices "
                          "SET phone_num='%1', name='%2',passwd='%3', memo='%4',bat_change='%5'   "
                          "WHERE id=%6 ;").arg(phone).arg(name).arg(passwd).arg(memo).arg(bat_ch).arg(id);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(upd) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    is_mole_cached_ = false;
    return true;
}

std::vector<MoleLine>& ArchiveDB::getMoles()
{
    if( is_mole_cached_ )
        return mole_cache_;

    QString get = QString("select id,phone_num,name,passwd,memo,bat_change from devices where hidden=0 ORDER BY id;");
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(get) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
           return mole_cache_;
       }

    mole_cache_.clear();

    while(query.next())
        {
            MoleLine line;
            for(uint i = 0; i < line.size(); ++i)
                line[i] = query.value(i).toString();
            mole_cache_.push_back(line);
        }
    is_mole_cached_ = true;

    return mole_cache_;
}

bool ArchiveDB::hideMoles(const QStringList &id_list)
{
    QString upd = QString("UPDATE devices "
                          "SET  hidden=1  "
                          "WHERE id IN (%1) ;").arg(id_list.join(","));
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(upd) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    is_mole_cached_ = false;
    return true;
}

std::vector<OutgoingLine> ArchiveDB::getNonSended()
{
    QString get = QString("select id,recipient,text,state from outgoing where state!=-1  ORDER BY recipient;");
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(get) )
       {
           logError(l)<<"SQL Error:"<<query.lastError().text();
           return std::vector<OutgoingLine>();
       }
    std::vector<OutgoingLine> res;

    while(query.next())
        {
            OutgoingLine line;
            for(uint i = 0; i < line.size(); ++i)
                line[i] = query.value(i).toString();
            res.push_back(line);
        }

    return res;
}

void ArchiveDB::setOutgoingState(const QString &id, int state)
{
    QString set = QString("UPDATE outgoing "
                          "SET state=%1    "
                          "WHERE id=%2;    ").arg(state).arg(id);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(set) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
       }
}

bool ArchiveDB::addSmsToOutgoing(int idx, const QString& txt)
{
    QString add = QString("INSERT INTO outgoing"
                          "(text,recipient,date,state)"
                          "VALUES ('%1',%2,datetime('now','localtime'),0);").arg(txt).arg(idx);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(add) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    if( !query.exec("SELECT max(id) FROM outgoing;"))
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    std::vector<int> sms_ids;
    while(query.next())
        sms_ids.push_back(query.value(0).toInt());

    if(sms_ids.size() > 0)
        {
            QString add_task = QString("INSERT INTO tasks"
                                       "(request,status,startdate)"
                                       "VALUES (%1, 0, datetime('now','localtime') );").arg(sms_ids[0]);
            if( !query.exec(add_task))
                {
                    logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                                 .arg(query.lastQuery())
                                 .arg(query.lastError().text());
                    return false;
                }
        }
    else
        {
            logError(l)<<tr("Неверное число полученных индексов");
        }
    return true;
}

bool ArchiveDB::saveIncomingSms(const QString& text, const QString& from, const QString& data)
{
    QString repl_txt = text;
    repl_txt.replace(QString("'"),QString("\\'")).replace(QString("\""),QString("\\\""));
    QString add_incoming = QString("INSERT INTO incoming"
                                   "(text,source,date,task_id)  "
                                   "VALUES ('%1','%2','%3',0);").arg(repl_txt).arg(from).arg(data);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(add_incoming) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    return true;
}

void ArchiveDB::setTaskState(int task_id, int state)
{
    QString set = QString("UPDATE tasks  "
                          "SET status=%1 "
                          "WHERE id=%2 ; ").arg(state).arg(task_id);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(set) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
        }
}

void ArchiveDB::setTaskDone(int task_id, int answer_id)
{
    QString set = QString("UPDATE tasks  "
                          "SET status=1,donedate=datetime('now','localtime'),answer=%1 "
                          "WHERE id=%2 ; ").arg(answer_id).arg(task_id);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(set) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
       }

    QString update_incoming = QString("UPDATE incoming "
                                      "SET task_id=%1  "
                                      "WHERE id=%2 ;   ").arg(task_id).arg(answer_id);
    if( !query.exec(update_incoming) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
       }
}

std::vector<TasksLine> ArchiveDB::getTasks(int status)
{
    QString get = QString("SELECT id,request,answer,startdate,donedate,status "
                          "FROM   tasks "
                          "WHERE  status=%1 "
                          "ORDER BY request ASC;").arg(status);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(get) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
           return std::vector<TasksLine>();
       }
    std::vector<TasksLine> res;

    while(query.next())
        {
            TasksLine line;
            for(uint i = 0; i < line.size(); ++i)
                line[i] = query.value(i).toString();
            res.push_back(line);
        }

    return res;
}

std::vector<OutgoingLine> ArchiveDB::getSended(int from, int till)
{
    QString get = QString("SELECT id,recipient,text,state "
                          "FROM  outgoing "
                          "WHERE id >= %1 AND id <= %2;").arg(from).arg(till);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(get) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
           return std::vector<OutgoingLine>();
       }
    std::vector<OutgoingLine> res;

    while(query.next())
        {
            OutgoingLine line;
            for(uint i = 0; i < line.size(); ++i)
                line[i] = query.value(i).toString();
            res.push_back(line);
        }

    return res;
}

std::vector<IncomingLine> ArchiveDB::getIncoming()
{
    QString get = QString("SELECT id,text,source,date "
                          "FROM  incoming "
                          "WHERE task_id = 0  ORDER BY id;");
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(get) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
           return std::vector<IncomingLine>();
       }
    std::vector<IncomingLine> res;

    while(query.next())
        {
            IncomingLine line;
            for(uint i = 0; i < line.size(); ++i)
                line[i] = query.value(i).toString();
            res.push_back(line);
        }

    return res;
}

IncomingLine ArchiveDB::getLastIncomingWithText(int num_id, const QString &txt)
{
    const std::vector<MoleLine>& moles = getMoles();
    auto mole_line = std::find_if(moles.begin(), moles.end(),
                                  [=](const MoleLine& ml)
                                  {return ml[MOLES_ID].toInt()==num_id;});

    if( mole_line == moles.end() )
        {
            logError(l)<<QStringLiteral("Не найден крот с индексом #")<<num_id;
            return IncomingLine();
        }

    QString get = QString("SELECT * FROM incoming "
                          "WHERE source='%1' AND  text LIKE '%%2%' "
                          "ORDER BY id DESC LIMIT 1;").arg( (*mole_line)[MOLES_PHONE]).arg(txt);
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(get) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
           return IncomingLine();
       }
    std::vector<IncomingLine> res;

    while(query.next())
        {
            IncomingLine line;
            for(uint i = 0; i < line.size(); ++i)
                line[i] = query.value(i).toString();
            res.push_back(line);
        }

    if(res.size())
        return res[0];
    else
        return IncomingLine();
}

std::vector<IncomingLine> ArchiveDB::getLastIncomingListWithText(const QStringList& nums,
                                                                 const QString& txt)
{
    QString part_get = QString("SELECT max(id),text,source,date FROM incoming                   "
                               "WHERE  source IN ( SELECT phone_num FROM devices WHERE id=%1  ) "
                               "AND  text LIKE '%%2%' ");
    QStringList getters;
    for(const QString id : nums)
        getters << part_get.arg(id).arg(txt);
    QString get = QString("SELECT * FROM (%1) WHERE text NOT NULL;").arg(getters.join("  UNION  "));
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(get) )
       {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
           return std::vector<IncomingLine>();
       }
    std::vector<IncomingLine> res;
    while(query.next())
        {
            IncomingLine line;
            for(uint i = 0; i < line.size(); ++i)
                line[i] = query.value(i).toString();
            res.push_back(line);
        }

    return res;
}

ArchiveDB::ArchiveDB(QObject *parent) :
    QObject(parent),
    is_mole_cached_(false),
    is_task_cached_(false)
{
    connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(stop())  );
}

void ArchiveDB::start()
{
    if(data_name_.size()==0)
        data_name_ = QString("%1/%2").arg(Application::instance()->dataDir()).arg("srvdata.sqlite");
    if( !QFile::exists(data_name_))
        makeDB();
    else
        connectToDB();
}

void ArchiveDB::connectToDB()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(data_name_);
    if (db.open())
        {
            logInfo(l)<<tr("Connect to DB successed.");
        }
    else
        {
            logError(l)<<db.lastError().text();
        }
}

bool ArchiveDB::makeDB()
{
    connectToDB();
    QSqlQuery query(QSqlDatabase::database());
    if( !query.exec(tbl_devices) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    if( !query.exec(tbl_incoming) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    if( !query.exec(tbl_outgoing) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    if( !query.exec(tbl_tasks) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    if( !query.exec(tbl_version) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    if( !query.exec(set_scheme_version) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }

    if( !query.exec(add_sleeper_1) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    if( !query.exec(add_sleeper_2) )
        {
            logError(l)<<QString(tr("Запрос ""%1"" завершился ошибкой %2 "))
                         .arg(query.lastQuery())
                         .arg(query.lastError().text());
            return false;
        }
    logInfo(l)<<tr("Recreate DB scheme.");
    return true;
}

void ArchiveDB::stop()
{
    logInfo(l)<<QStringLiteral("Closing DB connections.");
}
